package gov.fnal.mcginnis.threetone;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.swing.ImageIcon;

/**
 * @author mcginnis
 *
 */
public class BufferedImageWrapper extends BufferedImage
{
	/**
	 * @param iwidth
	 * @param height
	 */
	public BufferedImageWrapper(int iwidth, int height)
	{
		super(iwidth,height,BufferedImage.TYPE_INT_ARGB);
	}
	/**
	 * @param bsource
	 */
	public BufferedImageWrapper(BufferedImage bsource)
	{
		this(bsource.getWidth(),bsource.getHeight());
		int maxX = bsource.getWidth();
		int maxY = bsource.getHeight();
		for (int ii = 0; ii < maxX; ++ii)
		{
			for (int ij = 0; ij < maxY; ++ij)
			{
				setRGB(ii, ij, bsource.getRGB(ii, ij));
			}
		}
	}
	/**
	 * @param bsource
	 * @param satChange
	 */
	public BufferedImageWrapper(BufferedImage bsource, double satChange)
	{
		this(bsource.getWidth(),bsource.getHeight());
		int maxX = bsource.getWidth();
		int maxY = bsource.getHeight();
		for (int ii = 0; ii < maxX; ++ii)
		{
			for (int ij = 0; ij < maxY; ++ij)
			{
				Color	clr  = new Color(bsource.getRGB(ii, ij));
				int ired = clr.getRed();
				int igreen = clr.getGreen();
				int iblue = clr.getBlue();
				float[] hsbvals = new float[3];
				Color.RGBtoHSB(ired, igreen, iblue, hsbvals);
				hsbvals[1] = hsbvals[1] + (float) satChange;
				if (hsbvals[1] > (float) 1.0) hsbvals[1] = (float) 1.0;
				if (hsbvals[1] < (float) 0.0) hsbvals[1] = (float) 0.0;
				setRGB(ii, ij, Color.HSBtoRGB(hsbvals[0], hsbvals[1], hsbvals[2]));
			}
		}
	}
	/**
	 * @param bsource
	 * @param removeWhite
	 * @param removeBlack
	 */
	public BufferedImageWrapper(BufferedImage bsource, boolean removeWhite, boolean removeBlack)
	{
		this(bsource.getWidth(),bsource.getHeight());
		int maxX = bsource.getWidth();
		int maxY = bsource.getHeight();
		for (int ii = 0; ii < maxX; ++ii)
		{
			for (int ij = 0; ij < maxY; ++ij)
			{
				Color	clr  = new Color(bsource.getRGB(ii, ij));
				int ired = clr.getRed();
				int igreen = clr.getGreen();
				int iblue = clr.getBlue();
				float[] hsbvals = new float[3];
				Color.RGBtoHSB(ired, igreen, iblue, hsbvals);
				if (removeWhite) hsbvals[1] = (float) 1.0;
				if (removeBlack) hsbvals[2] = (float) 1.0;
				if (hsbvals[1] < (float) 0.0) hsbvals[1] = (float) 0.0;
				setRGB(ii, ij, Color.HSBtoRGB(hsbvals[0], hsbvals[1], hsbvals[2]));
			}
		}
	}
	/**
	 * @param bsource
	 * @param whiteFraction
	 * @param blackFraction
	 */
	public BufferedImageWrapper(BufferedImage bsource, double whiteFraction, double blackFraction)
	{
		this(bsource.getWidth(),bsource.getHeight());
		int maxX = bsource.getWidth();
		int maxY = bsource.getHeight();
		for (int ii = 0; ii < maxX; ++ii)
		{
			for (int ij = 0; ij < maxY; ++ij)
			{
				Color	clr  = new Color(bsource.getRGB(ii, ij));
				int ired = clr.getRed();
				int igreen = clr.getGreen();
				int iblue = clr.getBlue();
				float[] hsbvals = new float[3];
				Color.RGBtoHSB(ired, igreen, iblue, hsbvals);
				hsbvals[1] = (float) (hsbvals[1] + (1.0 - hsbvals[1]) * (1.0 - (float) whiteFraction));
				hsbvals[2] = (float) (hsbvals[2] + (1.0 - hsbvals[2]) * (1.0 - (float) blackFraction));
				setRGB(ii, ij, Color.HSBtoRGB(hsbvals[0], hsbvals[1], hsbvals[2]));
			}
		}
	}
	/**
	 * @param imageIcon
	 * @param width
	 * @param height
	 */
	public BufferedImageWrapper(ImageIcon imageIcon, int width, int height)
	{
		this(  (int) (BufferedImageWrapper.getCorrectAspectRatio(imageIcon, width, height) * ((double) imageIcon.getIconWidth())),
				(int) (BufferedImageWrapper.getCorrectAspectRatio(imageIcon, width, height) * ((double) imageIcon.getIconHeight())));
	 	Graphics2D graphics2D = createGraphics();
	    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
	    			RenderingHints.VALUE_INTERPOLATION_BILINEAR);
	    Image image = imageIcon.getImage();
	    graphics2D.drawImage(image, 0, 0, 
	    		(int) (BufferedImageWrapper.getCorrectAspectRatio(imageIcon, width, height) * ((double) imageIcon.getIconWidth())), 
	    		(int) (BufferedImageWrapper.getCorrectAspectRatio(imageIcon, width, height) * ((double) imageIcon.getIconHeight())), 
	    		null);
	}
	/**
	 * @param FImageFile
	 * @param width
	 * @param height
	 */
	public BufferedImageWrapper(File FImageFile, int width, int height)
	{
		this(new ImageIcon(FImageFile.getPath()), width, height);
	}
 
	private static double getCorrectAspectRatio(ImageIcon imageIcon, int width, int height)
	{
	 	double iconWidth = (double) imageIcon.getIconWidth();
	 	double iconHeight = (double) imageIcon.getIconHeight();
	 	double ratio = 1.0;
	 	if ((width > 0) && (height > 0))
	 	{
	 		double	dwidth =  (double) width;
	 		double	dheight = (double) height;
	 		double ratioX = dwidth / iconWidth;
	 		double ratioY = dheight / iconHeight;
	 		ratio = ratioX;
	 		if (ratioY < ratioX ) ratio = ratioY;
	 	}
	 	return ratio;
	}
	/**
	 * @param desiredAspectRatio
	 * @return int[][]
	 */
	public int[][] findCropCorners(double desiredAspectRatio)
	{
		int[][] icropCorners = new int[2][2];
		int maxX = getWidth() - 1;
		int maxY = getHeight() - 1;
		if (desiredAspectRatio < 0) 
		{
			icropCorners[0][0] = 0;
			icropCorners[0][1] = 0;
			icropCorners[1][0] = maxX;
			icropCorners[1][1] = maxY;
			return icropCorners;
		}
		int maxYnew;
		int maxXnew;
		
		double origAspectRatio = ((double) maxX)/ ((double) maxY);
		
		if (origAspectRatio > desiredAspectRatio)
		{
			maxYnew = maxY;
			double dmaxx = ((double) maxYnew) * desiredAspectRatio;
			maxXnew = (int) dmaxx;
		}
		else
		{
			maxXnew = maxX;
			double dmaxy = ((double) maxXnew) / desiredAspectRatio;
			maxYnew = (int) dmaxy;
		}
		icropCorners[0][0] = (maxX - maxXnew) / 2;
		icropCorners[0][1] = (maxY - maxYnew) / 2;
		icropCorners[1][0] = icropCorners[0][0] + maxXnew;
		icropCorners[1][1] = icropCorners[0][1] + maxYnew;
		return icropCorners;
	}
	/**
	 * @param icolor
	 * @param iNoXline
	 * @param iNoYline
	 * @param ithick
	 * @param desiredAspectRatio
	 */
	public void drawGridonImage(int icolor, int iNoXline, int iNoYline, int ithick, double desiredAspectRatio)
	{
		if ((iNoXline + iNoYline) <= 0) return;
		int[][] icropCorners = findCropCorners(desiredAspectRatio);
		int maxX = icropCorners[1][0] - icropCorners[0][0];
		int maxY = icropCorners[1][1] - icropCorners[0][1];
		for (int iline = 0; iline < iNoXline - 1; ++iline)
		{
			int ix = ((iline + 1) * maxX / iNoXline) + icropCorners[0][0];
			for (int it = ix - (ithick / 2); it <= ix + (ithick / 2); ++it)
			{
				for (int iy = icropCorners[0][1]; iy <= maxY + icropCorners[0][1]; ++iy )
				{
					setRGB(it, iy, icolor);
				}
			}
		}
		for (int iline = 0; iline < iNoYline - 1; ++iline)
		{
			int iy = ((iline + 1) * maxY / iNoYline) + icropCorners[0][1];
			for (int it = iy - (ithick / 2); it <= iy + (ithick / 2); ++it)
			{
				for (int ix = icropCorners[0][0]; ix <= maxX + icropCorners[0][0]; ++ix )
				{
					setRGB(ix, it, icolor);
				}
			}
		}
		// draw outline
		for (int it = 0; it <= ithick; ++it)
		{
			for (int iy = icropCorners[0][1]; iy <= maxY + icropCorners[0][1]; ++iy )
			{
				setRGB(icropCorners[0][0] + it, iy, icolor);
				setRGB(icropCorners[1][0] - it, iy, icolor);
			}
		}
		for (int it = 0; it <= ithick; ++it)
		{
			for (int ix = icropCorners[0][0]; ix <= maxX + icropCorners[0][0]; ++ix )
			{
				setRGB(ix, icropCorners[0][1] + it, icolor);
				setRGB(ix, icropCorners[1][1] - it, icolor);
			}
		}
	}
	/**
	 * @param ix
	 * @param iy
	 * @param isize
	 * @return int
	 */
	public int getAveragePixelColor(int ix, int iy, int isize)
	{
		int maxX = getWidth() - 1;
		int maxY = getHeight() - 1;
		int ired = 0;
		int igreen = 0;
		int iblue = 0;
		int icount = 0;
		for (int ii = ix - isize; ii <= ix - isize; ++ii)
		{
			if ( (0 <= ii) && (ii <= maxX))
			{
				for (int ij = iy - isize; ij <= iy - isize; ++ij)
				{
					if ( (0 <= ij) && (ij <= maxY))
					{
						Color clr = new Color(getRGB(ii, ij));
						ired = ired + clr.getRed();
						igreen = igreen + clr.getGreen();
						iblue = iblue + clr.getBlue();
						icount = icount + 1;
					}
				}
			}
		}
		double davg = ((double) ired) / ((double) icount);
		ired = (int) davg;
		davg = ((double) igreen) / ((double) icount);
		igreen = (int) davg;
		davg = ((double) iblue) / ((double) icount);
		iblue = (int) davg;
		Color clr = new Color(ired,igreen,iblue);
		return clr.getRGB();
	}
	/**
	 * @param ixc
	 * @param iyc
	 * @param ixsize
	 * @param iysize
	 * @return BufferedImageWrapper
	 */
	public BufferedImageWrapper getZoomedImage(int ixc, int iyc, int ixsize, int iysize)
	{
		int maxX = getWidth() - 1;
		int maxY = getHeight() - 1;
		int idx = ixsize;
		int idy = iysize;
		
		if ( (ixc + idx / 2) > maxX)
		{
			idx = 2 * (maxX - ixc);
		}
		if ( (ixc - idx / 2) < 0)
		{
			idx = 2 * ixc;
		}
		if ( (iyc + idy / 2) > maxY)
		{
			idy = 2 * (maxY - iyc);
		}
		if ( (iyc - idy / 2) < 0)
		{
			idy = 2 * iyc;
		}
		BufferedImageWrapper bsubimage = new BufferedImageWrapper(getSubimage(ixc - idx / 2, iyc - idy / 2, idx, idy));
		return bsubimage;
	}
	/**
	 * @param bclockWise
	 * @return BufferedImageWrapper
	 */
	public BufferedImageWrapper rotateImage(boolean bclockWise)
	{
		int width = getWidth();
		int height = getHeight();
		BufferedImageWrapper bRotateImage = new BufferedImageWrapper(height, width);
		for (int ii = 0; ii < width; ++ii)
		{
			for (int ij = 0; ij < height; ++ij)
			{
				if (!bclockWise)
				{
					bRotateImage.setRGB(ij, ii, getRGB(width - 1 - ii, ij));
				}
				else
				{
					bRotateImage.setRGB(ij, ii, getRGB(ii, height - 1 - ij));
				}
			}
		}
		return bRotateImage;
	}
	/**
	 * @param xstart
	 * @param ystart
	 * @param xstop
	 * @param ystop
	 * @param icolor
	 * @param bextend
	 */
	public void drawLineOnImage(int xstart, int ystart, int xstop, int ystop, int icolor, boolean bextend)
	{
		double slope = ((double) (ystop - ystart)) / ((double) (xstop - xstart) + 1.0e-10);
		double intercept = ((double) ystart) - slope * ((double) xstart);
		int maxX = getWidth() - 1;
		int maxY = getHeight() - 1;
		int xbegin = xstart;
		int xend = xstop;
		int ybegin = ystart;
		int yend = ystop;
		if (xstop < xstart)
		{
			xbegin = xstop;
			xend = xstart;
		}
		if (bextend)
		{
			xbegin = 0;
			xend = maxX;
		}
		if (ystop < ystart)
		{
			ybegin = ystop;
			yend = ystart;
		}
		if (bextend)
		{
			ybegin = 0;
			yend = maxY;
		}
		if (Math.abs(slope) < 1.0)
		{
			for (int ix = xbegin; ix <= xend; ++ix)
			{
				double dy = slope * ((double) ix) + intercept;
				int iy = (int) dy;
				if ((0 <= iy) && (iy <= maxY))
				{
					setRGB(ix, iy, icolor);
				}
			}
		}
		else
		{
			for (int iy = ybegin; iy <= yend; ++iy)
			{
				double dx = (((double) iy) - intercept) / slope;
				int ix = (int) dx;
				if ((0 <= ix) && (ix <= maxX))
				{
					setRGB(ix, iy, icolor);
				}
			}
			
		}
	}
}
